/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    diff;
  IndexCursor cursor;

  // Keep track of which conditions we've found
  bool eqcond = false;
  bool ltcond = false;
  bool gtcond = false;
  bool lecond = false;
  bool gecond = false;
  int eqcondKey;

  // If we have a range, keep track of the min and max keys. Initialize them to
  // min int and max int values;
  int minkey = MIN_KEY;
  int maxkey = MAX_KEY;

  // Keep track of the bounds of any range
  int ltval = MAX_KEY;
  int leval = MAX_KEY;
  int gtval = MIN_KEY;
  int geval = MIN_KEY;

  // Keep track of all tuples we've found that fit our criteria.
  vector<KVBag> keyTuples;
  vector<KVBag> valTuples;
  vector<KVBag> tuplesToPrint;
  vector<KVBag>::iterator itk;
  vector<KVBag>::iterator itv;
  vector<KVBag>::iterator it;
  KVBag curr;

  // Keep track of the condition types
  int keyConds = 0;
  bool valCondsExist = false;
  bool neCondExist = false;
  bool noKeyConds = true;
  for (int i = 0; i < cond.size(); ++i)
  {
    if (cond[i].attr == 2)
      valCondsExist = true;
    if (cond[i].attr == 1)
      noKeyConds = false;
    if (cond[i].attr == 2 && cond[i].comp == SelCond::NE)
      neCondExist = true;
  }

  // Determine if we never look at values in our query by ensuring that no
  // condition refers to values and that we are only selecting keys or a count
  // from our tuple
  bool dontNeedValues = true;
  for (int i = 0; i < cond.size(); i++)
    if (cond[i].attr == 2)
      dontNeedValues = false;
  dontNeedValues = (dontNeedValues && ((attr == 1) || attr == 4));

  //Open correct RecordFile
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}

  // Get the index ready for searching.
  BTreeIndex * bti = new BTreeIndex();
  rc = bti->open(table + ".idx", 'r');
  if (rc != 0)
    goto exit_select;

  // Look through all the conditions and address the ones that use the index.
  // Use this to determine bounds for the tuples based on the keys.
  //cout << "Starting to look through conditions" << endl;
  for (unsigned i = 0; i < cond.size(); i++)
  {
    //cout << "index cond[i]: " << i << endl;

    if (cond[i].attr == 1 && cond[i].comp != SelCond::NE)
    {
      keyConds += 1;
      //cout << "USING THE INDEX" << endl;

      switch (cond[i].comp)
      {
        case SelCond::EQ:
          //cout << "eqcond" << endl;
          rc = bti->locate(atoi(cond[i].value), cursor);

          if (rc == RC_NO_SUCH_RECORD)
            continue;
          else if (rc != 0)
            goto exit_select;

          rc = bti->readForward(cursor, key, rid);

          //cout << "found key: " << key << endl;

          if (rc != 0)
            goto exit_select;

          // If we found the key, it's the only thing that can be in our list
          if (key == atoi(cond[i].value))
          {
            // If this condition doesn't rely on the value, don't get the value
            if (cond[i].attr == 1 && attr != 2 && attr != 3)
              value = "";
            else
              rf.read(rid,key,value);

            curr.key = key;
            curr.value = value;

            //cout << "adding (" << key << ", " << value
            //     << ") to list " << 0 << endl;

            // If there was a prior eqcond then we check if there are 2
            // conflicting eqconds and make all tuples empty accordingly.
            if (eqcond && eqcondKey != key)
            {
              keyTuples.clear();
              goto print_results;
            }

            keyTuples.push_back(curr);

            eqcond = true;
            eqcondKey = key;
          }
          else
            goto next_cond;

          break;
        case SelCond::LT:
          ltcond = true;
          ltval = MIN(ltval, atoi(cond[i].value));
          break;
        case SelCond::GT:
          gtcond = true;
          gtval = MAX(gtval, atoi(cond[i].value));
          break;
        case SelCond::LE:
          lecond = true;
          leval = MIN(ltval, atoi(cond[i].value));
          break;
        case SelCond::GE:
          gecond = true;
          geval = MAX(geval, atoi(cond[i].value));
          break;
        default:
          goto exit_select;
          break;
      }
    }

    next_cond:
    //cout << "next_cond" << endl;
    continue;
  }

  //cout << eqcond << ltcond << lecond << gtcond << gecond << endl;

  //cout << "2" << endl;
  if (!eqcond && (ltcond || lecond || gtcond || gecond))
  {
    //cout << "Using the range conditions" << endl;
    //cout << "not eq cond" << endl;
    // Add all tuples that fit within our bounds
    bool lt, gt, le, ge;
    lt = gt = le = ge = false;
    if (ltcond && !lecond)
    {
      maxkey = ltval;
      lt = true;
    }
    else if (!ltcond && lecond)
    {
      maxkey = leval;
      le = true;
    }
    else if (ltcond && lecond)
    {
      if (leval == ltval)
      {
        maxkey = ltval;
        lt = true;
      }
      else if (ltval < leval)
      {
        maxkey = ltval;
        lt = true;
      }
      else if (ltval > leval)
      {
        maxkey = leval;
        le = true;
      }
    }

    if (gtcond && !gecond)
    {
      minkey = gtval;
      gt = true;
    }
    else if (!gtcond && gecond)
    {
      minkey = geval;
      ge = true;
    }
    else if (gtcond && gecond)
    {
      if (geval == gtval)
      {
        minkey = gtval;
        gt = true;
      }
      else if (gtval < geval)
      {
        minkey = geval;
        ge = true;
      }
      else if (gtval > geval)
      {
        minkey = gtval;
        gt = true;
      }
    }

    //cout << "minkey: " << minkey << endl;
    //cout << "maxkey: " << maxkey << endl;

    rc = bti->locate(minkey, cursor);
    if (rc != 0)
      goto exit_select;
    rc = bti->readForward(cursor, key, rid);
    if (rc != 0)
      goto exit_select;

    if (gt && key == minkey)
      bti->readForward(cursor, key, rid);

    //cout << key << endl;

    while (key <= maxkey)
    {
      // If this condition doesn't rely on the value, don't get the value
      if (dontNeedValues)
        value = "";
      else
        rf.read(rid,key,value);

      //cout << "read forward cursor: " << cursor.pid << ", "
      //                                << cursor.eid << endl;
      //cout << "read forward key: " << key << endl;

      if (valCondsExist && !neCondExist)
      {
        for (int i = 0; i < cond.size(); ++i)
        {
          if (cond[i].attr == 2)
          {
            diff = strcmp(value.c_str(), cond[i].value);

            // skip the tuple if any condition is not met
            switch (cond[i].comp) {
              case SelCond::EQ:
                if (diff != 0) continue;
                break;
              case SelCond::NE:
                if (diff == 0) continue;
                break;
              case SelCond::GT:
                if (diff <= 0) continue;
                break;
              case SelCond::LT:
                if (diff >= 0) continue;
                break;
              case SelCond::GE:
                if (diff < 0) continue;
                break;
              case SelCond::LE:
                if (diff > 0) continue;
                break;
            }

            curr.key = key;
            curr.value = value;
            keyTuples.push_back(curr);
          }
        }
      }
      else
      {
        curr.key = key;
        curr.value = value;
        keyTuples.push_back(curr);
      }

      //cout << "adding (" << key << ", " << value << ")" << endl;

      rc = bti->readForward(cursor, key, rid);
      if ((rc == 0 && cursor.eid == -1 && cursor.pid == -1) ||
          (key == maxkey && lt))
      {
        //cout << "Tuples from this condition:" << endl;
        //for (int j = 0; j < tuples[i].size(); ++j)
        //  cout << tuples[i][j].key << " " << endl;
        //cout << "last one" << endl;
        break;
      }
      else if (rc != 0)
      {
        //cout << "done reading from index and exiting now" << endl;
        goto exit_select;
        //break;
      }
    }
  }

  if ((keyConds == cond.size() && cond.size() != 0) || eqcond)
    goto print_results;

  //cout << "3" << endl;
  //cout << neCondExist << endl;

  if (noKeyConds || neCondExist)
  {
  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  while (rid < rf.endRid()) {
    //cout << "SCANNING THE TABLE" << endl;

    // read the tuple
    rc = rf.read(rid, key, value);
    if (rc < 0) {
      fprintf(stderr,
          "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // Special case for when there are no conditions
    if (cond.size() == 0)
    {
      curr.key = key;
      curr.value = value;
      valTuples.push_back(curr);
    }

    //cout << "Looking at (" << key << ", " << value << ")" << endl;

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {

      //cout << "table cond[i]: " << i << endl;

      // Skip conditions that use the index (we addressed them already).
      if (cond[i].attr == 1 && cond[i].comp != SelCond::NE)
      {
        //cout << "condition " << i << " uses the index; skipping" << endl;
        //goto next_tuple;
        continue;
      }

      //cout << "handling a non-index condition" << endl;

      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
        case 1:
          diff = key - atoi(cond[i].value);
          break;
        case 2:
          diff = strcmp(value.c_str(), cond[i].value);
          break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
        case SelCond::EQ:
          if (diff != 0) goto next_tuple;
          break;
        case SelCond::NE:
          if (diff == 0) goto next_tuple;
          break;
        case SelCond::GT:
          if (diff <= 0) goto next_tuple;
          break;
        case SelCond::LT:
          if (diff >= 0) goto next_tuple;
          break;
        case SelCond::GE:
          if (diff < 0) goto next_tuple;
          break;
        case SelCond::LE:
          if (diff > 0) goto next_tuple;
          break;
      }

      // Add the latest tuple to the right conditions list
      curr.key = key;
      curr.value = value;
      //cout << "adding (" << key << ", " << value << ") to list " << i << endl;
      valTuples.push_back(curr);

    }

    // move to the next tuple
    next_tuple:
    //cout << "going to next tuple" << endl;
    ++rid;
  }
  }

  print_results:

  // Create an intersection of the lists

  //if (keyTuples.size() == 0 && valTuples.size() != 0)
  //  tuplesToPrint = valTuples;
  //else if (keyTuples.size() == 0 && valTuples.size() == 0)
  //  tuplesToPrint = keyTuples;
  //else
  //{
  //  sort(keyTuples.begin(), keyTuples.end());
  //  sort(valTuples.begin(), valTuples.end());
  //  set_intersection(keyTuples.begin(), keyTuples.end(),
  //                   valTuples.begin(), valTuples.end(),
  //                   back_inserter(tuplesToPrint));
  //}

  //for (int i = 0; i < keyTuples.size(); ++i)
  //  cout << "Key tuple:" << keyTuples[i].key << endl;

  //for (int i = 0; i < valTuples.size(); ++i)
  //  cout << "Val tuple:" << valTuples[i].key << endl;

 
  // Intersect the lists in a noob way. Oh well.
  for (int i = 0; i < keyTuples.size(); ++i)
    if ((valTuples.size() == 0)
        ||
        (find(valTuples.begin(), valTuples.end(), keyTuples[i]) !=
                                                          valTuples.end()
        &&
        find(tuplesToPrint.begin(), tuplesToPrint.end(), keyTuples[i]) ==
                                                      tuplesToPrint.end()))
      tuplesToPrint.push_back(keyTuples[i]);

  for (int i = 0; i < valTuples.size(); ++i)
    if ((keyTuples.size() == 0)
        ||
        (find(keyTuples.begin(), keyTuples.end(), valTuples[i]) !=
                                                          keyTuples.end()
        &&
        find(tuplesToPrint.begin(), tuplesToPrint.end(), valTuples[i]) ==
                                                      tuplesToPrint.end()))
      tuplesToPrint.push_back(valTuples[i]);
  

  // Weird bug where sometimes end up with double tuple where one key is 0
  for (int i = 0; i < tuplesToPrint.size()-1; ++i)
    if (strcmp(tuplesToPrint[i].value.c_str(),
          tuplesToPrint[i+1].value.c_str()) == 0
        &&
        (tuplesToPrint[i+1].key == 0))
    {
      tuplesToPrint.erase(tuplesToPrint.begin() + i+1);
      i = -1;
    }


  //sort(tuplesToPrint.begin(), tuplesToPrint.end());

  //it = unique(tuplesToPrint.begin(), tuplesToPrint.end());
  //tuplesToPrint.resize(it - tuplesToPrint.begin());

  // tuplesToPrint has been created. Ready to print now!

  //for (int i = 0; i < tuplesToPrint.size(); i++)
  //  cout << tuplesToPrint[i].key << endl;

  //cout << "4" << endl;
  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", tuplesToPrint.size());
  }
  rc = 0;

  // print all tuplesToPrint
  for (int i = 0; i < tuplesToPrint.size(); ++i)
  {
    switch (attr) {
      case 1:  // SELECT key
        fprintf(stdout, "%d\n", tuplesToPrint[i].key);
        break;
      case 2:  // SELECT value
        fprintf(stdout, "%s\n", tuplesToPrint[i].value.c_str());
        break;
      case 3:  // SELECT *
        fprintf(stdout, "%d '%s'\n", tuplesToPrint[i].key,
                                     tuplesToPrint[i].value.c_str());
        break;
    }
  }

  // close the table file and return
  exit_select:
  rf.close();
  bti->close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	int key;
	string value;

	//open record file
	RecordFile rf;
	RecordId rid;
	rf.open(table + ".tbl", 'w');

	//open source file
	string line;
	ifstream myfile(loadfile.c_str());

	//open index file if needed
  BTreeIndex * bti;
	if (index) {
		bti = new BTreeIndex();
		bti->open(table + ".idx", 'w');
	}

	if(myfile.is_open())
	{
		while (myfile.good())
		{
			getline(myfile,line);
			if (line.empty())
				break;
			parseLoadLine(line,key,value);
			rf.append(key,value,rid);
			if (index) {
				bti->insert(key,rid);
			}
		}
		myfile.close();
		if (index) {
			bti->close();
		}
	}
	else return RC_FILE_OPEN_FAILED;


  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}

